/**
 * Copyright 2010 Victor Egea Hernando
 *
 * RockScript is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 * 
 * RockScript is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with RockScript.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "ustring.h"

#include "utf8d.h"

#include <iomanip>
#include <iostream>
#include <fstream>

namespace corelib{

/*------------------------------------------------------------------
   coder & bom flags
  ------------------------------------------------------------------*/

// UTF-8 Masks
#define MASKBITS   0x3F
#define MASKBYTE   0x80
#define MASK2BYTES 0xC0
#define MASK3BYTES 0xE0
#define MASK4BYTES 0xF0
// UTF-16 Masks
#define MASKBYTE1  0xD800
#define MASKBYTE2  0xDC00
// BOM characters
#define BOM1       0xEF
#define BOM2       0xBB
#define BOM3       0xBF

/*------------------------------------------------------------------
   class implementation
  ------------------------------------------------------------------*/

ustring::ustring()
{
    _length = 0;
    _above_BMP = false;
}

ustring::ustring( const ustring &str )
{
    copy( str );
}

ustring::ustring( const char *str )
{
    assign( str );
}

ustring::ustring( const std::string &str )
{
    assign( str );
}

ustring::ustring( const uch *ucodes, corelib::size n )
{
    assign( ucodes, n );
}

uint32 ustring::length( void ) const
{
    return _length;
}

bool ustring::hasBOM( const char *str )
{
    if(static_cast<uint8>(str[0])==BOM1){
        if(static_cast<uint8>(str[1])!=BOM2){
            return false;
        }else if(static_cast<uint8>(str[2])!=BOM3){
            return false;
        }
        return true;
    }
    return false;
}

void ustring::assign( const uch *ucodes, corelib::size n )
{
    _above_BMP = false; _ustr.clear(); _length = 0;
    // transform each unicode character
    for(corelib::size it=0;it<n;it++){
	push_back( ucodes[it] );
    }
}

void ustring::assign( const std::string &str )
{
    assign( str.c_str() );
}

void ustring::assign( const char *s )
{
    _above_BMP = false; _ustr.clear(); _length = 0;
    // set initial positions
    const char *cs = s; 
    // has a BOM?
    if( hasBOM(s) ){
        cs += 3;
    }
    // set codepoint & state
    uch cp = 0; uint32 state = UTF8_ACCEPT;
    // decode string
    for (; *cs; cs++){
        if ( !decode(state, cp, *cs & 0xFF) ){
            // increment length
            _length++;
            // convert to utf-16
            if(cp<0x10000){
                _ustr.push_back( static_cast<utf16u>(cp) );
            }else if(cp<0x110000){
                // It contains a 32 bit character
                _above_BMP = true;	
                // it must be transformed to a surrogated pair
                uch cp2 = cp - 0x10000;
                _ustr.push_back( static_cast<utf16u>(((cp2>>10)&0x3FF)|MASKBYTE1) );
                _ustr.push_back( static_cast<utf16u>((cp2&0x3FF)|MASKBYTE2) );
            }
        }else if( state==UTF8_REJECT ){
            break; // break loop
        }
    }

    if (state!=UTF8_ACCEPT){
        throw std::domain_error("the utf8 string is not well-formed");
    }
}

void ustring::copy( const ustring &str )
{
    _ustr = str._ustr;
    _length = str._length;
    _above_BMP = str._above_BMP;
}

void ustring::clear( void )
{
    _above_BMP = false; _ustr.clear();
}

bool ustring::empty( void ) const
{
    return _ustr.empty();
}

bool ustring::above_bmp( void ) const
{
    return _above_BMP;
}

std::string ustring::str( void ) const
{
    std::string str = "";
    // get length
    uint32 length = _ustr.size();

    // iterate through string
    for(uint32 it = 0;it<length;it++){
        // lets assume it's a UCS-2 code
        uch uc = _ustr[it];
        // convert to unicode if it's not (detect surrogated pairs)
        if( (uc>=0xD800 && uc<=0xDBFF) && (it+1 < length) ){
            utf16u uc2 = _ustr[it+1];
            // check second byte
            if( uc2>=0xDC00 && uc2<=0xDFFF ){
                // decode utf-16 to unicode
                it++; uc = (((uc&0x3FF)<<10)|(uc2&0x3FF)) + 0x10000;
            }
        }

        // code to utf8
        if( uc < 0x80 ){
            str.push_back(static_cast<uint8>(uc));
        }else if( uc < 0x800 ){
            str.push_back(static_cast<uint8>(MASK2BYTES | (uc >> 6)));
            str.push_back(static_cast<uint8>(MASKBYTE | (uc & MASKBITS)));
        }else if( uc < 0x10000 ){
            str.push_back(static_cast<uint8>(MASK3BYTES | (uc >> 12)));
            str.push_back(static_cast<uint8>(MASKBYTE | ((uc >> 6) & MASKBITS)));
            str.push_back(static_cast<uint8>(MASKBYTE | (uc & MASKBITS)));
        }else if( uc < 0x110000 ){
            str.push_back(static_cast<uint8>(MASK4BYTES | (uc >> 18)));
            str.push_back(static_cast<uint8>(MASKBYTE | ((uc >> 12) & MASKBITS)));
            str.push_back(static_cast<uint8>(MASKBYTE | ((uc >> 6) & MASKBITS)));
            str.push_back(static_cast<uint8>(MASKBYTE | (uc & MASKBITS)));
        }else{
            // throw an error
            std::stringstream ss; ss << "the unicode character U+";
            ss.setf(std::ios::hex, std::ios::basefield);
            ss << std::uppercase << std::setw(8) << std::setfill('0');
            ss << uc << " cannot be encoded in utf-8";
            throw std::domain_error(ss.str().c_str());
        }
    }

    return str;
}

ustring ustring::substr( uint32 pos1, uint32 pos2 )
{
    // check domain
    CORELIB_ASSERT( !_above_BMP ) 
    if( pos2==npos ) pos2 = length() - 1;
    CORELIB_ASSERT( pos2>=pos1 && pos2<length() );

    // returning string
    ustring str;
    // return string
    str._ustr.assign( &_ustr[pos1], &_ustr[++pos2] );
    return str;
}

void ustring::push_back( uch ch )
{
    // convert to utf-16
    if(ch<0x10000){
        _ustr.push_back( static_cast<utf16u>(ch) );
    }else if(ch<0x110000){
        // it contains a 32 bit character
        _above_BMP = true;
        // it must be transformed to a surrogated pair
        uch ch2 = ch - 0x10000;
        _ustr.push_back( static_cast<utf16u>(((ch2>>10)&0x3FF)|MASKBYTE1) );
        _ustr.push_back( static_cast<utf16u>((ch2&0x3FF)|MASKBYTE2) );
    }else{
        // throw an error
        std::stringstream ss; ss << "the unicode character U+";
        ss.setf(std::ios::hex, std::ios::basefield);
        ss << std::uppercase << std::setw(8) << std::setfill('0');
        ss << ch << " cannot be encoded in utf-16";
        throw std::domain_error(ss.str().c_str());
    }
    // increment length
    _length++;
}

ustring::uch ustring::operator[]( size idx ) const
{
     CORELIB_ASSERT( !_above_BMP );
     CORELIB_ASSERT( idx < _ustr.size() );
     return _ustr[idx];
}

ustring ustring::operator+( const ustring &str ) const
{
    ustring stro;
    // concatenate strings
    stro._length += str._length;
    std::copy(_ustr.begin(), _ustr.end(), std::back_inserter(stro._ustr));
    std::copy(str._ustr.begin(), str._ustr.end(), std::back_inserter(stro._ustr));
    stro._above_BMP = _above_BMP || str._above_BMP;
    // return resulting string
    return stro;
}

ustring ustring::operator+( const char *str ) const
{
    ustring stro; ustring str2(str);
    // concatenate strings
    stro._length += str2._length;
    std::copy(_ustr.begin(), _ustr.end(), std::back_inserter(stro._ustr));
    std::copy(str2._ustr.begin(), str2._ustr.end(), std::back_inserter(stro._ustr));
    stro._above_BMP = _above_BMP || str2._above_BMP;
    // return resulting string
    return stro;
}

ustring ustring::operator+( const std::string &str ) const
{
    ustring stro; ustring str2(str);
    // concatenate strings
    stro._length += str2._length;
    std::copy(_ustr.begin(), _ustr.end(), std::back_inserter(stro._ustr));
    std::copy(str2._ustr.begin(), str2._ustr.end(), std::back_inserter(stro._ustr));
    stro._above_BMP = _above_BMP || str2._above_BMP;
    // return resulting string
    return stro;
}

int32 ustring::compare( const ustring &str ) const
{
    uint32 it = 0; int32 cmp = 0;
    uint32 length1 = _ustr.size();
    uint32 length2 = str._ustr.size();

    while( it<length1 && it<length2 ){
        cmp = _ustr[it] - str._ustr[it];
        // return comparition
        if(cmp!=0) return cmp;
        // increment iterator
        it++;
    }

    // strings with diferent length
    return ( it==length2 ) - ( it==length1 );
}

bool ustring::operator == ( const ustring& str ) const
{
    return compare( str ) == 0;
}

bool ustring::operator < ( const ustring& str ) const
{
    return compare( str ) < 0;
}

size_t ustring_hash::operator () ( const ustring &str ) const
{
    uint32 len = str._ustr.size() * 2;

    // hash seed
    const uint32 seed = 0;

    /*
     * 'm' and 'r' are mixing constants generated offline.
     * They're not really 'magic', they just happen to work well.
     */
    const uint32 m = 0x5bd1e995;
    const int32 r = 24;

    // Initialize the hash to a 'random' value
    size_t h = seed ^ len;

    // Mix 4 bytes at a time into the hash
    const unsigned char *data = (const unsigned char *)&str._ustr[0];

    while(len >= 4){
        int32 k = *(corelib::int32 *)data;

        k *= m;
        k ^= k >> r;
        k *= m;

        h *= m;
        h ^= k;

        data += 4;
        len -= 4;
    }

    // Handle the last few bytes of the input array
    switch(len){
        case 3: h ^= data[2] << 16;
        case 2: h ^= data[1] << 8;
        case 1: h ^= data[0];
        h *= m;
    };

    /*
     * Do a few final mixes of the hash to ensure the last few
     * bytes are well-incorporated.
     */
    h ^= h >> 13;
    h *= m;
    h ^= h >> 15;

    // return hash
    return h;
}

}; // namespace corelib;

std::ostream &operator << ( std::ostream &os, const corelib::ustring &utf8s )
{
    os << utf8s.str();
    // return output stream
    return os;
}
